/*   Copyright 2010 Jonathan Wagner
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package com.alienos.collide.math;

import java.io.Serializable;

public final class Vector3 implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public static final Vector3 ZERO = new Vector3(0, 0, 0);
    public static final Vector3 ONE = new Vector3(1, 1, 1);
    public static final Vector3 UP = new Vector3(0, 1, 0);
    public static final Vector3 DOWN = new Vector3(0, -1, 0);
    public static final Vector3 RIGHT = new Vector3(1, 0, 0);
    public static final Vector3 LEFT = new Vector3(-1, 0, 0);
    public static final Vector3 FORWARD = new Vector3(0, 0, 1);
    public static final Vector3 BACKWARD = new Vector3(0, 0, -1);

    public float getX() {
        return x;
    }

    public float getY() {
        return y;
    }

    public float getZ() {
        return z;
    }

    private final float x;
    private final float y;
    private final float z;

    public Vector3(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public float dot(float x, float y, float z) {
        return (getX() * x) + (getY() * y) + (getZ() * z);
    }

    public float dot(Vector3 v) {
        return dot(v.getX(), v.getY(), v.getZ());
    }

    public Vector3 subtract(Vector3 v) {
        return new Vector3(x - v.getX(), y - v.getY(), z - v.getZ());
    }

    public Vector3 add(Vector3 v) {
        return new Vector3(x + v.getX(), y + v.getY(), z + v.getZ());
    }

    public Vector3 add(float fx, float fy, float fz) {
        return new Vector3(x + fx, y + fy, z + fz);
    }

    public Vector3 multiply(float scalar) {
        return new Vector3(x * scalar, y * scalar, z * scalar);
    }

    public Vector3 multiply(Vector3 v) {
        return new Vector3(x * v.x, y * v.y, z * v.z);
    }

    public Vector3 divide(Vector3 v) {
        return new Vector3(x / v.getX(), y / v.getY(), z / v.getZ());
    }

    public Vector3 divide(float scalar) {
        return new Vector3(x / scalar, y / scalar, z / scalar);
    }

    public Vector3 cross(Vector3 v) {

        float newX = (getY() * v.getZ()) - (getZ() * v.getY());
        float newY = (getZ() * v.getX()) - (getX() * v.getZ());
        float newZ = (getX() * v.getY()) - (getY() * v.getY());
        return new Vector3(newX, newY, newZ);
    }

    public float distanceSquared(float x, float y, float z) {
        float dx = getX() - x;
        float dy = getY() - y;
        float dz = getZ() - z;
        return dx * dx + dy * dy + dz * dz;
    }

    public float distanceSquared(Vector3 v) {
        return distanceSquared(v.getX(), v.getY(), v.getZ());
    }

    public float distance(float x, float y, float z) {
        return Mathf.squareRoot(distanceSquared(x, y, z));
    }

    public float distance(Vector3 v) {
        return distance(v.getX(), v.getY(), v.getZ());
    }

    public Vector3 lerp(Vector3 end, float scalar) {
        float x = (1.0f - scalar) * getX() + scalar * end.getX();
        float y = (1.0f - scalar) * getY() + scalar * end.getY();
        float z = (1.0f - scalar) * getZ() + scalar * end.getZ();
        return new Vector3(x, y, z);
    }

    public static Vector3 lerp(Vector3 begin, Vector3 end, float scalar) {
        if (!begin.equals(end)) {
            float x = (1.0f - scalar) * begin.getX() + scalar * end.getX();
            float y = (1.0f - scalar) * begin.getY() + scalar * end.getY();
            float z = (1.0f - scalar) * begin.getZ() + scalar * end.getZ();
            return new Vector3(x, y, z);
        } else {
            // Hey, I was already there!
            return begin;
        }
    }

    public Vector3 normalize() {

        float fLength = (float) Math.sqrt(x * x + y * y + z * z);

        if (fLength > 1e-08) {
            float fInvLength = 1.0f / fLength;
            return new Vector3(x * fInvLength, y * fInvLength, z * fInvLength);
        }

        return this;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Float.floatToIntBits(x);
        result = prime * result + Float.floatToIntBits(y);
        result = prime * result + Float.floatToIntBits(z);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Vector3 other = (Vector3) obj;
        if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x))
            return false;
        if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y))
            return false;
        if (Float.floatToIntBits(z) != Float.floatToIntBits(other.z))
            return false;
        return true;
    }

    public float squareMagnitude() {
        return (x * x + y * y + z * z);
    }

    public float length() {
        return Mathf.squareRoot(lengthSquared());
    }

    public float lengthSquared() {
        return getX() * getX() + getY() * getY() + getZ() * getZ();
    }

    @Override
    public String toString() {
        return "Vector3 [x=" + x + ", y=" + y + ", z=" + z + "]";
    }

}
